﻿using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;

namespace CodeJumper
{
    public class CodeJumper : Canvas, IWpfTextViewMargin
    {
        public const string MarginName = "CodeJumper";
        private readonly Thickness _marginSplitter;
        private readonly HorizontalAlignment _splitterAlignment;
        private readonly IWpfTextView _textView;
        private ApplicationConfiguration _appConfig;
        private bool _documentClosed;
        private DTE _dte;
        private bool _isControlEnabled = true;
        private bool _isDisposed;

        private HorizontalAlignment _marginAlignment;
        private double _savedGridSplitterWidth = 300;

        /// <summary>
        /// Creates a <see cref="CodeJumper"/> for a given <see cref="IWpfTextView"/>.
        /// </summary>
        /// <param name="textView">The <see cref="IWpfTextView"/> to attach the margin to.</param>
        public CodeJumper(IWpfTextView textView, Thickness MarginSplitter, HorizontalAlignment SplitterAlignment)
        {
            _marginSplitter = MarginSplitter;
            _splitterAlignment = SplitterAlignment;


            // This should stay the first line as we otherwise can't load StaticConfiguration images
            if (Application.ResourceAssembly == null)
                Application.ResourceAssembly = Assembly.GetExecutingAssembly();


            // Add trace listener. Something is fucked for addins
            var def = new DefaultTraceListener();
            Trace.Listeners.Clear();
            Trace.Listeners.Add(def);

            Background = Brushes.Transparent;
            Focusable = true;
            _textView = textView;
            // this.Loaded += new RoutedEventHandler(MarginLoaded);

            MarginLoaded(this, null);
        }

        #region IWpfTextViewMargin Members

        /// <summary>
        /// The <see cref="Sytem.Windows.FrameworkElement"/> that implements the visual representation
        /// of the margin.
        /// </summary>
        public FrameworkElement VisualElement
        {
            // Since this margin implements Canvas, this is the object which renders
            // the margin.
            get
            {
                ThrowIfDisposed();
                return this;
            }
        }

        public double MarginSize
        {
            // Since this is a horizontal margin, its width will be bound to the width of the text view.
            // Therefore, its size is its height.
            get
            {
                ThrowIfDisposed();
                return ActualHeight;
            }
        }

        public bool Enabled
        {
            // The margin should always be enabled
            get
            {
                ThrowIfDisposed();
                return _isControlEnabled;
            }
        }

        /// <summary>
        /// Returns an instance of the margin if this is the margin that has been requested.
        /// </summary>
        /// <param name="marginName">The name of the margin requested</param>
        /// <returns>An instance of EditorMargin1 or null</returns>
        public ITextViewMargin GetTextViewMargin(string marginName)
        {
            return (marginName == MarginName) ? this : null;
        }

        public void Dispose()
        {
            if (!_isDisposed)
            {
                GC.SuppressFinalize(this);
                _isDisposed = true;
            }
        }

        #endregion

        private void InvokeDeletionUpdate(CodeElement element)
        {
            // DispatcherPriority must be Loaded because the Dispatcher is disable during Layout phase. So we have to wait until it's done.
            if (_documentClosed)
            {
                Trace.WriteLine(string.Format("Skipping parsing of {0}", _appConfig.DocumentFilename));
                return;
            }

            _appConfig.InvokeDeletionUpdate(element);
        }

        private void InvokeCompleteUpdate()
        {
            // DispatcherPriority must be Loaded because the Dispatcher is disable during Layout phase. So we have to wait until it's done.
            if (_documentClosed)
            {
                Trace.WriteLine(string.Format("Skipping parsing of {0}", _appConfig.DocumentFilename));
                return;
            }


            _appConfig.InvokeCompleteUpdate();
        }


        private void DocumentOpened(Document document)
        {
            InstanceManager.Instance.AddInstance(_appConfig);
            Trace.WriteLine(string.Format("Document openend {0}", document.Name));
            if (document.FullName == _appConfig.DocumentFilename)
            {
                if (_appConfig.DocumentProjectItem.FileCodeModel == null)
                {
                    Trace.WriteLine(string.Format("No FileCodeModel available. {0}", _appConfig.DocumentFilename));

                    Visibility = Visibility.Collapsed;
                    _isControlEnabled = false;
                    return;
                }

                if (_appConfig.DocumentProjectItem.FileCodeModel.CodeElements == null)
                {
                    Trace.WriteLine(string.Format("No code elements available.", document.FullName));
                    return;
                }

                InvokeCompleteUpdate();
            }
        }

        private void DocumentClosing(Document document)
        {
            if (document.FullName == _appConfig.DocumentFilename)
            {
                InstanceManager.Instance.RemoveInstance(_appConfig);
                Trace.WriteLine(string.Format("Document closing {0}", _appConfig.DocumentFilename));
                _documentClosed = true;
            }
        }

        private void MarginLoaded(object sender, RoutedEventArgs e)
        {
            Trace.WriteLine("MarginLoaded");
            
            SetupApplication();

            #region Code model events

            // This could happen if we open a file that is outside the our project, so we have no "ProjectItem"
            if (_appConfig.DocumentProjectItem == null)
            {
                Visibility = Visibility.Collapsed;
                _isControlEnabled = false;
                return;
            }

            if (_appConfig.DocumentProjectItem.FileCodeModel != null)
            {
                Trace.WriteLine(string.Format("Early Filecodemodel ready {0}", _appConfig.DocumentFilename));
                InvokeCompleteUpdate();
            }

            #endregion

            _textView.ViewportHeightChanged += ViewportHeightChanged;

            Height = _textView.ViewportHeight;
            Width = 200;
            _appConfig.elementView.Width = Width;
            SizeChanged += MarginSizeChanged;
            ClipToBounds = true;

            Children.Add(_appConfig.elementView);
        }

        private void SetupApplication()
        {
            _appConfig = new ApplicationConfiguration();

            // Set up mouse processor for scrolling
            _appConfig.elementView = new ElementView(_appConfig);
            Func<ElementView> viewFunc = delegate { return _appConfig.elementView; };
            _textView.Properties.GetOrCreateSingletonProperty("ElementViewUI", viewFunc);


            // Set up application

            _appConfig.ApplicationId = InstanceManager.Instance.Random.Next();


            // Retrieve Project item
            InstanceManager.Instance.InstanceHint(_appConfig);
            _appConfig.HookInformation = InstanceManager.Instance.HookInformation;

            _appConfig.DocumentFilename = ProjectHelper.GetFilePath(_textView);
            _appConfig.DocumentProjectItem = ProjectHelper.GetProjectItem(_appConfig, _appConfig.DocumentFilename);
            _appConfig.DocumentParser = new DocumentParser(_appConfig);


            _appConfig.LoadThemes();
            Background = Brushes.Transparent;

            Trace.WriteLine(string.Format("Application Id {0} for {1}", _appConfig.ApplicationId, _appConfig.DocumentFilename));

            // AppConfig & local settings
            _appConfig.ExpanderStyle = _appConfig.elementView.FindResource("ExpanderStyle1") as Style;
            _appConfig.EasyButtonStyle = _appConfig.elementView.FindResource("EasyButton") as Style;
            _appConfig.HideVariables = PluginConfiguration.Instance.HideVariables == YesNoType.Yes;
            _appConfig.HideProperties = PluginConfiguration.Instance.HideProperties == YesNoType.Yes;

            // Setup alignment
            _marginAlignment = HorizontalAlignment.Right;
            _appConfig.elementView.splitter1.HorizontalAlignment = _splitterAlignment; // Splitter Left or right
            _appConfig.elementView.SplitterMarginGrid.Margin = _marginSplitter; // Margin of Grid Left or right
            if (_splitterAlignment == HorizontalAlignment.Right)
            {
                _marginAlignment = HorizontalAlignment.Left;
                _appConfig.elementView.scrollViewer1.Style = _appConfig.elementView.FindResource("SimpleScrollViewerLeft") as Style;
            }

            #region Splitter Init

            _appConfig.elementView.splitter1.Background = _appConfig.ViewPortSplitterBackgroundBrush;
            _appConfig.elementView.splitter1.DragDelta += (s, ev) =>
            {
                if (_marginAlignment == HorizontalAlignment.Right)
                {
                    if (Width - ev.HorizontalChange < 1)
                    {
                        Width = 5;
                        return;
                    }
                    Width -= ev.HorizontalChange;
                }
                else
                {
                    if (Width + ev.HorizontalChange < 1)
                    {
                        Width = 5;
                        return;
                    }
                    Width += ev.HorizontalChange;
                }
                _savedGridSplitterWidth = Width;
            };

            _appConfig.elementView.splitter1.MouseDoubleClick += (s, ev) =>
            {
                if (Width == 5)
                {
                    if (_savedGridSplitterWidth == 5)
                        _savedGridSplitterWidth = 300;

                    Width = _savedGridSplitterWidth;
                }
                else
                {
                    _savedGridSplitterWidth = Width;
                    Width = 5;
                }
            };

            #endregion

            _appConfig.elementView.SetParent(this);
        }

        private void ElementDeleted(object parent, CodeElement element)
        {
            Trace.WriteLine(string.Format("Element deleted {0}", element.Name));
            if (_appConfig.DocumentFilename == element.ProjectItem.FileNames[0])
            {
                Trace.WriteLine(string.Format("({0}) Element deleted. {1}", _appConfig.ApplicationId, element.ProjectItem.FileNames[0]));
                InvokeDeletionUpdate(element);
            }
        }

        private void ElementAdded(CodeElement element)
        {
            Trace.WriteLine(string.Format("Element added {0}", element.Name));
            if (_appConfig.DocumentFilename == element.ProjectItem.FileNames[0])
            {
                Trace.WriteLine(string.Format("({0}) Element added. {1}", _appConfig.ApplicationId, element.ProjectItem.FileNames[0]));
                InvokeCompleteUpdate();
            }
        }

        private void ElementChanged(CodeElement element, vsCMChangeKind change)
        {
            Trace.WriteLine(string.Format("Element changed {0}", element.Name));

            if (_appConfig.DocumentFilename == element.ProjectItem.FileNames[0])
            {
                Trace.WriteLine(string.Format("({0}) Element changed.", _appConfig.ApplicationId));
                InvokeCompleteUpdate();
            }
        }


        private void MarginSizeChanged(object sender, SizeChangedEventArgs e)
        {
            _appConfig.elementView.Width = e.NewSize.Width;
        }


        private void ViewportHeightChanged(object sender, EventArgs e)
        {
            Trace.WriteLine(string.Format("Viewport height changed to {0}", _textView.ViewportHeight));
            Height = _textView.ViewportHeight;
            _appConfig.elementView.Height = Height;
        }

        private void ThrowIfDisposed()
        {
            if (_isDisposed)
                throw new ObjectDisposedException(MarginName);
        }
    }
}